#include "llvm.h"
#include "debug.h"
#include "tabfun.h"
#include "ast_types.h"

#define DEBUG 0

// Private function declaration
char* plist_to_llvm(ll_list* params);
char* alist_to_llvm(ll_list* args);


int next_num = 0 ;
// TODO: why return char?
char new_reg() {
	return next_num++;
}

char* alloc() {
	return (char*) malloc(sizeof(char)*100);
}

// Public function definitions
char* gen_next_lblstr() {
    static int idx = 0;
    char* lbl = (char*)malloc(sizeof(char)*100);
    sprintf(lbl, "@str.%d", idx++);
    return lbl;
}

void gen_br(int depth, char const* br) {
    printf("\tbr label %%%s.%d\n", br, depth);
}

void gen_cond_br(char const* reg, int depth, char const* br1, char const* br2) {
    printf("\tbr i1 %%%s, label %%%s.%d, label %%%s.%d\n",
           reg, br1, depth, br2, depth
    );
}

void gen_label(char const* lbl, int depth) {
    printf("%s.%d:\n", lbl, depth);
}

/** If type of op1 or op2 != t, hoist them to t. */
void gen_cast(Type t, Lhs_type* op) {
    if (op->type == t) return;

    if (t == t_float) {
        char *newreg;
        new_reg(); newreg=alloc(); sprintf(newreg,"r%d",next_num);
        printf("\t%%%s = uitofp %s %%%s to %s\n",
               newreg, trub_to_tllvm(op->type), op->sval, trub_to_tllvm(t));
        op->sval = newreg;
        op->type = t;
    } else {
        debug_print("Can not convert %s to %s\n", pp_type(op->type), pp_type(t));
        assert(false); // not implemented...
    }
}

void gen_start_func(Fonction* f, bool variadic) {
    printf("define %s @%s(%s%s) {\n",
           trub_to_tllvm(f->ret_type),
           f->nom,
           plist_to_llvm(f->args),
           variadic ? ", ..." : ""
    );
}

void gen_end_func() {
    printf("}\n");
}

void gen_call(Fonction* f, ll_list* args) {
    char *alist = alist_to_llvm(args);
    debug_print("generating function call%s\n", "");
    printf("\t" "call %s @%s(%s)\n",
           trub_to_tllvm(f->ret_type),
           f->nom,
           alist
    );
}

void gen_ret(Type t, char* val) {
    printf("\t" "ret %s %s" "\n", trub_to_tllvm(t), val);
}

void gen_fdecl(Type ret, char const *nom, ll_list* params, bool variadic) {
    printf("declare %s @%s(%s%s)\n",
           trub_to_tllvm(ret),
           nom,
           plist_to_llvm(params),
           variadic ? ", ..." : ""
    );
}
void gen_pre_while(char* deep)
{
	char *Wlabel = (char*) malloc(sizeof(char));
	strcpy(Wlabel,"Wlabel");
	strcat(Wlabel,deep);
	printf("\tbr label %%%s\n",Wlabel);
	printf("%s: \n",Wlabel);
	free(Wlabel);
}
void gen_while(char* reg,char* deep)
{		
	char *label1 = (char *) malloc(sizeof(char));	
	strcpy(label1,"lTrue");
	strcat(label1,deep);	
	char *label2 = (char *) malloc(sizeof(char)); 
	strcpy(label2,"lFalse");
	strcat(label2,deep);	
	
	printf("\tbr i1 %%%s, label %%%s, label %%%s\n",reg,label1,label2);
	printf("%s: \n", label1);
}

void gen_end_while(char* deep)
{
		
	char *Wlabel = (char *) malloc(sizeof(char));
	//sprintf(Wlabel,"Wlabel%d",deep);
	strcpy(Wlabel,"Wlabel");
	strcat(Wlabel,deep);
	printf("\tbr label %%%s\n",Wlabel);

	char *label1 = (char *) malloc(sizeof(char)); 
	//sprintf(label1,"lFalse%d",deep);
	strcpy(label1,"lFalse");
	strcat(label1,deep);
	printf("%s: \n", label1);

	char *Elabel = (char *) malloc(sizeof(char));
	strcpy(Elabel,"Elabel");
	strcat(Elabel,deep);

	printf("\tbr label %%%s\n",Elabel);
	
	printf("%s: \n", Elabel);

}

void gen_pre_for(int reg,char* reg1,char* reg2,char *deep)
{
	char *Flabel = (char*) malloc(sizeof(char));
	strcpy(Flabel,"Flabel");
	strcat(Flabel,deep);
	printf("br label %%%s\n",Flabel);
	printf("%s: \n",Flabel);
	printf("%%v%s = load i32* %%%s\n",deep,reg1);
	printf("%%r%d = icmp ule i32 %%v%s,%%%s\n",reg,deep,reg2);
	free(Flabel);
}
void gen_for(int reg,char* deep)
{
		
	char *label1 = (char *) malloc(sizeof(char));	
	strcpy(label1,"lfTrue");
	strcat(label1,deep);	
	char *label2 = (char *) malloc(sizeof(char)); 
	strcpy(label2,"lfFalse");
	strcat(label2,deep);	
	
	printf("br i1 %%r%d, label %%%s, label %%%s\n",reg,label1,label2);
	printf("%s: \n", label1);
	
	
} 
void gen_end_for(char* deep,char *indice)
{
	printf("%%t%s = load i32* %%%s\n",deep,indice);
	printf("%%t%s%s = add i32 %%t%s,1\n",deep,deep,deep);
	printf("store i32 %%t%s%s, i32* %%%s\n",deep,deep,indice);
	char *Flabel = (char *) malloc(sizeof(char));
	strcpy(Flabel,"Flabel");
	strcat(Flabel,deep);
	printf("br label %%%s\n",Flabel);

	char *label1 = (char *) malloc(sizeof(char)); 
	strcpy(label1,"lfFalse");
	strcat(label1,deep);
	printf("%s: \n", label1);

	char *Elabel = (char *) malloc(sizeof(char));
	strcpy(Elabel,"Eflabel");
	strcat(Elabel,deep);

	printf("br label %%%s\n",Elabel);	
	
	printf("%s: \n", Elabel);
}
char* gen_numbinop(char op, Type t, Lhs_type* op1, Lhs_type* op2) {
    char *res_reg;

    gen_cast(t, op1);
    gen_cast(t, op2);

    new_reg();
    res_reg = alloc();
    sprintf(res_reg, "r%d", next_num);

    char *llop;

    if (op == '+' && t == t_int) llop = "add";
    if (op == '+' && t == t_float) llop = "fadd";
    if (op == '-' && t == t_int) llop = "sub";
    if (op == '-' && t == t_float) llop = "fsub";
    if (op == '*' && t == t_int) llop = "mul";
    if (op == '*' && t == t_float) llop = "fmul";
    if (op == '/' && t == t_int) llop = "udiv";
    if (op == '/' && t == t_float) llop = "fdiv";

    printf("\t%%r%d = %s %s %%%s,%%%s\n",
           next_num, llop, trub_to_tllvm(t)
           , op1->sval, op2->sval);

    return res_reg;
}
// Private functions

char const* trub_to_tllvm(Type t) {
    switch (t) {
        case t_int: return "i32";
        case t_float: return "double";
        case t_bool: return "i1";
        case t_chaine: return "i8*";
        case t_void: return "void";
        default:
            debug_print("unknown type: %s\n", pp_type(t));
            assert(false);
            break;
    }
}

char* alist_to_llvm(ll_list* args) {
    // TODO: memory mgmt...
    int size = ll_size(args), i;
    char *res = (char*)malloc(sizeof(char)*size*20);
    strcpy(res, "");

    for (i = 0; i < size; i++) {
        if (i < size - 1) {
            res = strcat(res, ", ");
        }

        Lhs_type* lhs_type = (Lhs_type*)ll_get(args, i);
        char const* tstr = trub_to_tllvm(lhs_type->type);

        res = strcat(res, tstr);
        res = strcat(res, " %"); // TODO: ugly
        res = strcat(res, lhs_type->sval);
    }
    return res;
}

char* plist_to_llvm(ll_list* params) {
    // TODO: memory mgmt...
    int size = ll_size(params), i;
    char *res = (char*)malloc(sizeof(char)*size*6);
    Type* p;
    strcpy(res, "");
    for (i = 0; i < size; i++) {
        char const* tstr = trub_to_tllvm(*(Type *)ll_get(params, i));
        assert(strlen(tstr) <= 4);
        if (i < size - 1) {
            res = strcat(res, ", ");
        }
        res = strcat(res, tstr);
    }
    return res;
}
